
package atm.components;

import atm.components.mock.BankMock;
import atm.model.AtmState;
import atm.model.CreditCardInfo;
import atm.model.CreditCardVerificationStatus;
import atm.model.PinCodeVerificationStatus;
import atm.model.Transaction;
import atm.model.WithdrawStatus;

/**
 * Main controller class, that provides mechanisms for interaction between UI and ATM.
 */
public class AtmEngine {

	private AtmState atmState = AtmState.IDLE;

	private Transaction currentTransaction;

	private CreditCardReader creditCardReader;

	private Printer printer;

	private Bank bank;

	private CashDispenser cashDispenser;

	public AtmEngine() {
		setCreditCardReader(new CreditCardReader());
		setPrinter(new Printer());
		setBank(new BankMock());
		setCashDispenser(new CashDispenser());
	}

	/**
	 * Checks the printer status.
	 * 
	 * @return true if printer is operating
	 */
	public boolean checkPrinter() {
		if (getAtmState() != AtmState.CREDIT_CARD_READ)
			throw new IllegalStateException("Failed to read credit card");
		if (getCurrentTransaction() == null)
			throw new IllegalStateException("The current transaction");
		if (getPrinter() == null)
			throw new IllegalStateException("Reference to printer is null");

		boolean printerCheckResult = getPrinter().checkPaper();

		setAtmState(AtmState.PRINTER_CHECKED);

		return printerCheckResult;

	}

	/**
	 * Ends current transaction.
	 */
	public void endTransaction() {
		if (getAtmState() != AtmState.IDLE && getCurrentTransaction() != null) {

			setCurrentTransaction(null);
			setAtmState(AtmState.IDLE);
		}
	}

	public AtmState getAtmState() {
		return atmState;
	}

	public Bank getBank() {
		return bank;
	}

	public CashDispenser getCashDispenser() {
		return cashDispenser;
	}

	public CreditCardReader getCreditCardReader() {
		return creditCardReader;
	}

	public Transaction getCurrentTransaction() {
		return currentTransaction;
	}

	public Printer getPrinter() {
		return printer;
	}

	/**
	 * Reads a credit card that customer put in the reader.
	 * 
	 * @return true if credit card was read successfully
	 */
	public boolean readCreditCard() {
		if (getAtmState() != AtmState.WAITING_FOR_CREDIT_CARD)
			throw new IllegalStateException("ATM is in the wrong state");
		if (getCreditCardReader() == null)
			throw new IllegalStateException("Reference to credit card reader is null");

		// ATM will try to read the card maximum 3 times before it aborts
		CreditCardInfo tempCreditCardInfo = getCreditCardReader().readCreditCard(3);
		if (tempCreditCardInfo != null) {
			setAtmState(AtmState.CREDIT_CARD_READ);
			getCurrentTransaction().setCreditCardInfo(tempCreditCardInfo);
			return true;
		} else {
			setAtmState(AtmState.TRANSACTION_INVALID);
			return false;
		}
	}

	private void setAtmState(AtmState value) {
		atmState = value;
	}

	private void setBank(Bank value) {
		bank = value;
	}

	private void setCashDispenser(CashDispenser value) {
		cashDispenser = value;
	}

	private void setCreditCardReader(CreditCardReader value) {
		creditCardReader = value;
	}

	private void setCurrentTransaction(Transaction value) {
		currentTransaction = value;
	}

	private void setPrinter(Printer value) {
		printer = value;
	}

	/**
	 * Start a new transaction for a customer. May not start a new transaction if connection with bank is not valid. Previous transaction
	 * must be ended before new one is started.
	 * 
	 * @return true if transaction was started
	 */
	public boolean startTransacation() {
		if (getAtmState() != AtmState.IDLE)
			throw new IllegalStateException("ATM is in a wrong state");
		if (getCurrentTransaction() != null)
			throw new IllegalStateException("Current Transaction is null");
		if (getBank() == null)
			throw new IllegalStateException("Reference to bank is null");

		// Let's verify connection with bank. We cannot do anything without
		// them :)
		if (getBank().verifyConnection()) {
			setCurrentTransaction(new Transaction());
			setAtmState(AtmState.WAITING_FOR_CREDIT_CARD);
			return true;
		} else {
			setAtmState(AtmState.TRANSACTION_INVALID);
			return false;
		}
	}

	/**
	 * Verifies the credit card with a bank and returns a status.
	 * 
	 * @return reutrns OK if card was verified successfully, INVALID if card is corrupted or BLOCKED in case card was blocked
	 */
	public CreditCardVerificationStatus verifyCreditCard() {
		if (getAtmState() != AtmState.PRINTER_CHECKED)
			throw new IllegalStateException("ATM is in the wrong state");
		if (getCurrentTransaction() == null)
			throw new IllegalStateException("Current Transaction is null");
		if (getCurrentTransaction().getCreditCardInfo() == null)
			throw new IllegalStateException("The credit card info is null");
		if (getCurrentTransaction().getPinCode() != null)
			throw new IllegalStateException("The pincode is not null");
		if (getCurrentTransaction().getAmount() != null)
			throw new IllegalStateException("The amount is not null");
		if (getBank() == null)
			throw new IllegalStateException("Reference to the bank is null");

		CreditCardVerificationStatus status = getBank().verifyCreditCard(getCurrentTransaction().getCreditCardInfo());
		if (status == CreditCardVerificationStatus.OK) {
			setAtmState(AtmState.CARD_VERIFIED);
		} else if (status == CreditCardVerificationStatus.INVALID) {
			setAtmState(AtmState.TRANSACTION_INVALID);
		} else if (status == CreditCardVerificationStatus.BLOCKED) {
			setAtmState(AtmState.TRANSACTION_INVALID);
			// Credit card is blocked we need to print confirmation
			if (getPrinter().checkPaper()) {
				getPrinter().printCardWithheldConfirmation();
			}

		}
		return status;
	}

	/**
	 * Verifies the PIN code for card in current transaction.
	 * 
	 * @param pinCode
	 *            PIN code to verify
	 * @return OK if PIN was correct or FAILED_CARD_LOCKED if wrong PIN was entered too many times
	 */
	public PinCodeVerificationStatus verifyPinCode(String pinCode) {
		if (getAtmState() != AtmState.CARD_VERIFIED)
			throw new IllegalStateException("ATM is in a wrong state");
		if (getCurrentTransaction() == null)
			throw new IllegalStateException("Current Transaction is null");
		if (getCurrentTransaction().getCreditCardInfo() == null)
			throw new IllegalStateException("The Credit card info is null");
		if (getCurrentTransaction().getPinCode() != null)
			throw new IllegalStateException("The pincode is not null");
		if (getCurrentTransaction().getAmount() != null)
			throw new IllegalStateException("The amount is not null");
		if (pinCode == null)
			throw new IllegalStateException("The pincode is null");
		if (getBank() == null)
			throw new IllegalStateException("Reference to the bank is null");

		PinCodeVerificationStatus status = getBank().verifyPinCode(getCurrentTransaction().getCreditCardInfo().getNumber(), pinCode);
		if (status == PinCodeVerificationStatus.OK) {
			setAtmState(AtmState.PIN_VERIFIED);
			getCurrentTransaction().setPinCode(pinCode);
		} else if (status == PinCodeVerificationStatus.FAILED_CARD_LOCKED) {
			setAtmState(AtmState.TRANSACTION_INVALID);
			// Credit card is blocked we need to print confirmation
			if (getPrinter().checkPaper()) {
				getPrinter().printCardWithheldConfirmation();
			}
		}
		return status;
	}

	/**
	 * Withdraws given amount from credit card in current transaction.
	 * 
	 * @param amount
	 *            Amount to withdraw
	 * @return OK if cash can be withdrawn, FAILED_NOT_ENOUGH_FUNDS if there was not enough funds on credit card or
	 *         FAILED_NOT_ENOUGH_CASH_IN_ATM if ATM does not have enough funds
	 */
	public WithdrawStatus withdraw(int amount) {
		if (getAtmState() != AtmState.PIN_VERIFIED)
			throw new IllegalStateException("ATM is in a wrong state");
		if (getCurrentTransaction() == null)
			throw new IllegalStateException("Current Transaction is null");
		if (getCurrentTransaction().getCreditCardInfo() == null)
			throw new IllegalStateException("Credit card info is null");
		if (getCurrentTransaction().getPinCode() == null)
			throw new IllegalStateException("The pincode is null");
		if (getCurrentTransaction().getAmount() != null)
			throw new IllegalStateException("The amount is not null");
		if (amount > 0 && getCashDispenser() == null)
			throw new IllegalStateException("Reference to the cash dispenser is null");
		if (getCashDispenser().getDb() == null)
			throw new IllegalStateException("Reference to the database is null");
		if (getCashDispenser().getDb().getTrays() == null)
			throw new IllegalStateException("Reference to the trays is null");
		if (getCashDispenser().getDb().getTrays().getTrays() == null)
			throw new IllegalStateException("Reference to the trays is null");
		if (getCashDispenser().getDb().getTrays().getTrays().size() <= 0)
			throw new IllegalStateException("The size of the trays is 0 or less");

		WithdrawStatus status;

		// First let's check if ATM has enough cash
		if (getCashDispenser().isEnoughCash(amount)) {
			// If we have cash in ATM, lets withdraw from bank
			boolean withdrawStatus = getBank().withdrawAmount(getCurrentTransaction().getCreditCardInfo().getNumber(),
			        getCurrentTransaction().getPinCode(), amount);
			// So we have withdrawn it from bank, let's give it the the
			// client
			if (withdrawStatus) {
				getCashDispenser().ejectAmount(amount);

				// Credit card is blocked we need to print confirmation
				if (getPrinter().checkPaper()) {
					getPrinter().printTransactionConfirmation(amount);
				}

				status = WithdrawStatus.OK;
			} else {
				// I know that feeling bro...
				status = WithdrawStatus.FAILED_NOT_ENOUGH_FUNDS;
			}
		} else {
			// Well... people are rich nowadays? Maybe he will be also happy
			// with smaller amount?
			status = WithdrawStatus.FAILED_NOT_ENOUGH_CASH_IN_ATM;
		}

		return status;
	}
}
